Skip to content

Fix the gamma hook landing on a garbage vtable slot - #433

Merged
praydog merged 1 commit into
praydog:masterfrom
Remleo:fix-render-target-gamma
Aug 30, 2026
Merged

praydog merged 1 commit into
praydog:masterfrom
Remleo:fix-render-target-gamma

Conversation

@Remleo

@Remleo Remleo commented Aug 29, 2026 •

Copy link
Copy Markdown
Contributor

The check on the gamma index compares a std::optional with 0:

if (auto display_gamma_index = sdk::FRenderTarget::get_display_gamma_index(); display_gamma_index != 0) {

An empty optional compares as less than any number, so this is true exactly when the
index was not found. gamma_increase_fn then gets written at whatever slot the
empty optional produces, and the render target's vtable pointer is swapped to that
copy. In the log it looks like this:

Failed to find FRenderTarget::GetDisplayGamma vtable index
[FRenderTarget] Hooked FRenderTarget!

Uses has_value() now, and gives up if the index is past the end of the copied vtable
instead of writing outside it.

Also logs which of the two sources the gamma came from, once. A viewport reporting 2.2
and a missing viewport falling back to the constant 2.2 give the same picture, so there
was no way to tell a working hook from one that happens to look right.

On its own this turns a silent vtable corruption into a warning. For the index to be
found at all, the searches in UESDK need fixing too, which is a separate PR over there:
https://github.com/praydog/UESDK/pull/2. The two don't depend on each other to build or
merge, they just need each other to fix the dark eye.

Tested

Release build, three games with the native stereo fix on. With both changes the hook
installs and the second eye matches the first. Gamma values are from the new log line:

game UE GetDisplayGamma index gamma taken from the viewport
The Outer Worlds 2 5 7 1.9999999
Silent Hill 2 5 5 2.2
Gylt 4 4 2.4999998

Each value matches that game's own gamma setting. I changed The Outer Worlds 2 from 2.2
to 2.0 and the logged value followed, so it is reading the viewport and not the 2.2
fallback.

The check on the gamma index compared a std::optional with 0. An empty optional
compares as less than any number, so "display_gamma_index != 0" was true exactly when
the index had not been found. Dereferencing it wrote gamma_increase_fn at whatever
slot that produced, and the render target's vtable pointer was then swapped to that
copy. In the log it shows up as a failed search followed by "Hooked FRenderTarget!"
on the next line, and the second eye keeps the gamma the hook was supposed to fix.

Use has_value, and give up if the index is past the end of the copied vtable instead
of writing outside it.

Also log which of the two sources the gamma came from, once. A viewport that reports
2.2 and a missing viewport falling back to the constant 2.2 look the same on screen,
so there was no way to tell a working hook from one that happens to look right. Only
the first of them follows the game's own gamma setting.

On its own this turns a silent vtable corruption into a warning. For the index to be
found at all the searches in UESDK need fixing too, which is a separate change over
there. With both, the hook installs on The Outer Worlds 2, Silent Hill 2 and Gylt, at
gamma index 7, 5 and 4, and the second eye matches the first.
@praydog
praydog merged commit 0ff01bd into praydog:master Aug 30, 2026
1 check passed
@Remleo
Remleo deleted the fix-render-target-gamma branch August 30, 2026 10:20
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants